import sys
import os
import threading
import queue
import serial
import serial.tools.list_ports
import binascii
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from tkinter.scrolledtext import ScrolledText
from datetime import datetime

# ====================== 系统配置 ======================
COM_PORTS = [f"COM{i}" for i in range(1, 9)]  # 模拟8个COM口
DEFAULT_BAUDRATES = [9600, 19200, 38400, 57600, 115200, 230400, 460800]
DATA_FORMATS = ['HEX', 'ASCII', '二进制文件']
STYLE_CONFIG = {
    'progress.red': {'background': '#ff4444', 'troughcolor': '#ffe6e6'},
    'progress.yellow': {'background': '#ffd700', 'troughcolor': '#fff8e6'},
    'progress.green': {'background': '#44ff44', 'troughcolor': '#e6ffe6'}
}


# ====================== CRC计算函数 ======================
def calculate_crc(data):
    """CRC-16/MODBUS 校验计算"""
    crc = 0xFFFF
    for byte in data:
        crc ^= byte
        for _ in range(8):
            if crc & 0x0001:
                crc = (crc >> 1) ^ 0xA001
            else:
                crc >>= 1
    return crc.to_bytes(2, 'little')


# ====================== 串口通道类 ======================
class SerialChannel:
    def __init__(self, port, baudrate):
        self.port = port
        self.baudrate = baudrate
        self.ser = None
        self.rx_queue = queue.Queue()
        self.tx_queue = queue.Queue()
        self.running = False
        self.progress = 0
        self.status = "等待连接"
        self.error_code = 0

    def open(self):
        try:
            self.ser = serial.Serial(
                port=self.port,
                baudrate=self.baudrate,
                bytesize=serial.EIGHTBITS,
                parity=serial.PARITY_NONE,
                stopbits=serial.STOPBITS_ONE,
                timeout=0.1
            )
            self.running = True
            threading.Thread(target=self._rx_thread, daemon=True).start()
            threading.Thread(target=self._tx_thread, daemon=True).start()
            self.status = "已连接"
            return True
        except serial.SerialException as e:
            self.status = f"连接失败: {str(e)}"
            return False

    def close(self):
        self.running = False
        if self.ser and self.ser.is_open:
            self.ser.close()
        self.status = "已关闭"

    def _rx_thread(self):
        while self.running:
            try:
                if self.ser.in_waiting > 0:
                    data = self.ser.read(self.ser.in_waiting)
                    self.rx_queue.put(data)
            except Exception as e:
                self.status = f"接收错误: {str(e)}"
                break

    def _tx_thread(self):
        while self.running:
            try:
                if not self.tx_queue.empty():
                    data = self.tx_queue.get()
                    self.ser.write(data)
            except Exception as e:
                self.status = f"发送错误: {str(e)}"
                break


# ====================== 烧录核心类 ======================
class BurnManager:
    def __init__(self):
        self.channels = {port: SerialChannel(port, 115200) for port in COM_PORTS}
        self.file_data = b''
        self.current_format = '二进制文件'
        self.current_baudrate = 115200

    def set_format(self, fmt):
        self.current_format = fmt

    def set_baudrate(self, baudrate):
        self.current_baudrate = baudrate

    def _load_data(self, file_path):
        """多格式数据加载"""
        try:
            if self.current_format == '二进制文件':
                with open(file_path, 'rb') as f:
                    return f.read()

            elif self.current_format == 'HEX':
                with open(file_path, 'r') as f:
                    content = f.read().replace(' ', '').replace('\n', '')
                    if len(content) % 2 != 0:
                        raise ValueError("HEX数据长度必须为偶数")
                    return binascii.unhexlify(content)

            elif self.current_format == 'ASCII':
                with open(file_path, 'r', encoding='utf-8') as f:
                    return f.read().encode('utf-8')

            raise ValueError("未知格式")
        except Exception as e:
            raise ValueError(f"数据加载失败: {str(e)}")

    def start_burn(self, port, file_path):
        def burn_task():
            try:
                self.file_data = self._load_data(file_path)
                total = len(self.file_data)
                packet_size = 512

                # 更新波特率设置
                self.channels[port].baudrate = self.current_baudrate

                for i in range(0, total, packet_size):
                    if not self.channels[port].running:
                        break

                    # 构造数据包
                    chunk = self.file_data[i:i + packet_size]
                    header = i.to_bytes(4, 'big')
                    crc = calculate_crc(chunk)
                    packet = header + chunk + crc

                    # 发送数据
                    self.channels[port].tx_queue.put(packet)

                    # 更新进度
                    progress = (i + packet_size) / total * 100
                    self.channels[port].progress = min(progress, 100)

                    # 等待ACK
                    if not self._wait_ack(port):
                        raise TimeoutError("响应超时")

                self.channels[port].status = "烧录完成" if self.channels[port].running else "已中止"
            except Exception as e:
                self.channels[port].status = f"错误: {str(e)}"

        threading.Thread(target=burn_task, daemon=True).start()

    def _wait_ack(self, port, timeout=1):
        """等待设备确认"""
        start = datetime.now()
        while (datetime.now() - start).total_seconds() < timeout:
            if not self.channels[port].rx_queue.empty():
                ack = self.channels[port].rx_queue.get()
                return ack == b'\x06'
        return False


# ====================== 主界面类 ======================
class BurnerApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("智能串口烧录工具-V4.0")
        self.geometry("1280x800")
        self.burn_mgr = BurnManager()

        self._setup_ui()
        self._update_ui()

    def _setup_ui(self):
        """构建用户界面"""
        main_frame = ttk.Frame(self, padding=10)
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 控制面板
        control_frame = ttk.LabelFrame(main_frame, text="控制中心", padding=10)
        control_frame.pack(fill=tk.X, pady=5)

        # 端口选择
        ttk.Label(control_frame, text="目标端口:").grid(row=0, column=0)
        self.port_combo = ttk.Combobox(control_frame, values=COM_PORTS, state="readonly")
        self.port_combo.current(0)
        self.port_combo.grid(row=0, column=1, padx=5)

        # 波特率选择
        ttk.Label(control_frame, text="波特率:").grid(row=0, column=2)
        self.baud_combo = ttk.Combobox(
            control_frame,
            values=DEFAULT_BAUDRATES,
            state="readonly",
            width=10
        )
        self.baud_combo.current(4)  # 默认选择115200
        self.baud_combo.grid(row=0, column=3, padx=5)
        self.baud_combo.bind("<<ComboboxSelected>>", self._update_baudrate)

        # 数据格式选择
        ttk.Label(control_frame, text="数据格式:").grid(row=0, column=4)
        self.format_combo = ttk.Combobox(control_frame, values=DATA_FORMATS, state="readonly")
        self.format_combo.current(2)
        self.format_combo.grid(row=0, column=5, padx=5)
        self.format_combo.bind("<<ComboboxSelected>>", lambda e: self.burn_mgr.set_format(self.format_combo.get()))

        # 文件选择
        self.file_entry = ttk.Entry(control_frame, width=30)
        self.file_entry.grid(row=0, column=6, padx=5)
        ttk.Button(control_frame, text="浏览文件", command=self._select_file).grid(row=0, column=7)

        # 操作按钮
        btn_frame = ttk.Frame(control_frame)
        btn_frame.grid(row=0, column=8, padx=10)
        ttk.Button(btn_frame, text="连接端口", command=self._connect_port).pack(side=tk.LEFT)
        ttk.Button(btn_frame, text="开始烧录", command=self._start_burn).pack(side=tk.LEFT)
        ttk.Button(btn_frame, text="紧急停止", command=self._stop_burn).pack(side=tk.LEFT)

        # 状态监控面板
        status_frame = ttk.LabelFrame(main_frame, text="实时状态", padding=10)
        status_frame.pack(fill=tk.BOTH, expand=True)

        # 状态表格
        self.status_tree = ttk.Treeview(
            status_frame,
            columns=('port', 'baudrate', 'status', 'progress', 'errors'),
            show='headings'
        )
        self.status_tree.heading('port', text='端口')
        self.status_tree.heading('baudrate', text='波特率')
        self.status_tree.heading('status', text='状态')
        self.status_tree.heading('progress', text='进度')
        self.status_tree.heading('errors', text='错误代码')
        self.status_tree.pack(fill=tk.BOTH, expand=True)

        # 进度条
        style = ttk.Style()
        style.configure('red.Horizontal.TProgressbar', **STYLE_CONFIG['progress.red'])
        style.configure('yellow.Horizontal.TProgressbar', **STYLE_CONFIG['progress.yellow'])
        style.configure('green.Horizontal.TProgressbar', **STYLE_CONFIG['progress.green'])
        self.progress = ttk.Progressbar(main_frame, style='green.Horizontal.TProgressbar')
        self.progress.pack(fill=tk.X, pady=5)

        # 日志系统
        log_frame = ttk.LabelFrame(main_frame, text="操作日志", padding=10)
        log_frame.pack(fill=tk.BOTH, expand=True)
        self.log_text = ScrolledText(log_frame, wrap=tk.WORD, state='disabled')
        self.log_text.pack(fill=tk.BOTH, expand=True)

    def _update_baudrate(self, event):
        """更新波特率设置"""
        try:
            baud = int(self.baud_combo.get())
            self.burn_mgr.set_baudrate(baud)
            self._log(f"波特率更新为: {baud} bps")
        except ValueError:
            messagebox.showerror("错误", "无效的波特率设置")
            self.baud_combo.set(self.burn_mgr.current_baudrate)

    def _select_file(self):
        """文件选择"""
        file_types = [
            ('二进制文件', '*.bin'),
            ('HEX文件', '*.hex *.txt'),
            ('文本文件', '*.txt'),
            ('所有文件', '*.*')
        ]
        path = filedialog.askopenfilename(filetypes=file_types)
        if path:
            self.file_entry.delete(0, tk.END)
            self.file_entry.insert(0, path)

    def _connect_port(self):
        """连接端口"""
        port = self.port_combo.get()
        if self.burn_mgr.channels[port].open():
            self._log(f"端口 {port} 连接成功")
        else:
            messagebox.showerror("连接错误", f"无法连接 {port}")

    def _start_burn(self):
        """启动烧录"""
        port = self.port_combo.get()
        file_path = self.file_entry.get()

        if not file_path:
            messagebox.showwarning("输入错误", "请先选择烧录文件")
            return

        try:
            self.burn_mgr.start_burn(port, file_path)
            self._log(
                f"开始烧录 - 端口:{port} 格式:{self.burn_mgr.current_format} 波特率:{self.burn_mgr.current_baudrate}")
        except ValueError as e:
            messagebox.showerror("数据错误", str(e))

    def _stop_burn(self):
        """紧急停止"""
        port = self.port_combo.get()
        self.burn_mgr.channels[port].close()
        self._log(f"紧急停止端口 {port}")

    def _update_ui(self):
        """更新界面状态"""
        for port in COM_PORTS:
            channel = self.burn_mgr.channels[port]
            items = self.status_tree.get_children()

            if port not in items:
                self.status_tree.insert('', 'end', iid=port, values=(
                    port,
                    channel.baudrate,
                    channel.status,
                    f"{channel.progress:.1f}%",
                    channel.error_code
                ))
            else:
                self.status_tree.item(port, values=(
                    port,
                    channel.baudrate,
                    channel.status,
                    f"{channel.progress:.1f}%",
                    channel.error_code
                ))

            # 更新进度条颜色
            if channel.progress <= 33:
                self.progress.configure(style='red.Horizontal.TProgressbar')
            elif 34 <= channel.progress <= 66:
                self.progress.configure(style='yellow.Horizontal.TProgressbar')
            else:
                self.progress.configure(style='green.Horizontal.TProgressbar')

        self.after(500, self._update_ui)

    def _log(self, message):
        """日志记录"""
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        self.log_text.configure(state='normal')
        self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
        self.log_text.configure(state='disabled')
        self.log_text.see(tk.END)


if __name__ == "__main__":
    app = BurnerApp()
    app.mainloop()